use crate::config::AppConfig;
use crate::entity::users::{
    ActiveModel as UserActiveModel, Column as UsersColumn, Entity as Users,
};
use crate::error::AppError;
use crate::models::{User, UserCreate, UserLogin};
use crate::utils::{create_token, hash_password, verify_password};
use base64::{Engine as _, engine::general_purpose};
use regex::Regex;
use sea_orm::{
    ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, ModelTrait,
    PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, Set,
};
use tracing::{debug, info, warn};

pub struct AuthService {
    pub db: DatabaseConnection,
    config: AppConfig,
}

impl AuthService {
    pub fn new(db: DatabaseConnection, config: AppConfig) -> Self {
        Self { db, config }
    }

    pub fn config(&self) -> &AppConfig {
        &self.config
    }

    pub async fn validate_email_domain(&self, email: &str) -> Result<bool, AppError> {
        if self.config.auth.email_suffix_regex.is_empty() {
            return Ok(true);
        }

        let regex = Regex::new(&self.config.auth.email_suffix_regex)?;
        Ok(regex.is_match(email))
    }

    pub async fn authenticate_user(&self, credentials: &UserLogin) -> Result<User, AppError> {
        debug!("Attempting to authenticate user: {}", credentials.email);

        let user_model = Users::find()
            .filter(UsersColumn::Email.eq(&credentials.email))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::Unauthorized("Invalid credentials".to_string()))?;

        let user = User {
            id: user_model.id,
            email: user_model.email,
            hashed_password: user_model.hashed_password,
            avatar_url: user_model.avatar_url,
            is_admin: user_model.is_admin,
            created_at: chrono::DateTime::from_naive_utc_and_offset(
                user_model.created_at,
                chrono::Utc,
            ),
            updated_at: chrono::DateTime::from_naive_utc_and_offset(
                user_model.updated_at,
                chrono::Utc,
            ),
        };

        debug!("Verifying password for user: {}", credentials.email);
        let is_valid = verify_password(&credentials.password, &user.hashed_password)?;
        if !is_valid {
            warn!(
                "Authentication failed for user: {} - Invalid password",
                credentials.email
            );
            return Err(AppError::Unauthorized("Invalid credentials".to_string()));
        }

        info!("Authentication successful for user: {}", credentials.email);
        Ok(user)
    }

    pub async fn create_user(
        &self,
        user_data: &UserCreate,
        is_admin: bool,
    ) -> Result<User, AppError> {
        // Validate email domain
        if !is_admin && !self.validate_email_domain(&user_data.email).await? {
            return Err(AppError::Validation(
                "Email domain not allowed for registration".to_string(),
            ));
        }

        // Check if user already exists
        let existing_user = Users::find()
            .filter(UsersColumn::Email.eq(&user_data.email))
            .one(&self.db)
            .await?;

        if existing_user.is_some() {
            return Err(AppError::Conflict(
                "User with this email already exists".to_string(),
            ));
        }

        // Hash password
        let hashed_password = hash_password(&user_data.password)?;

        // Create new user
        let now = chrono::Utc::now();
        let new_user = UserActiveModel {
            email: Set(user_data.email.clone()),
            hashed_password: Set(hashed_password),
            avatar_url: Set(None),
            is_admin: Set(is_admin),
            created_at: Set(now.naive_utc()),
            updated_at: Set(now.naive_utc()),
            ..Default::default()
        };

        let user_model = new_user.insert(&self.db).await?;

        let user = User {
            id: user_model.id,
            email: user_model.email,
            hashed_password: user_model.hashed_password,
            avatar_url: user_model.avatar_url,
            is_admin: user_model.is_admin,
            created_at: chrono::DateTime::from_naive_utc_and_offset(
                user_model.created_at,
                chrono::Utc,
            ),
            updated_at: chrono::DateTime::from_naive_utc_and_offset(
                user_model.updated_at,
                chrono::Utc,
            ),
        };

        info!("Created new user: {}", user.email);
        Ok(user)
    }

    pub async fn create_token(&self, user: &User) -> Result<String, AppError> {
        create_token(&user.email, &self.config)
    }

    pub async fn login_user(&self, credentials: &UserLogin) -> Result<String, AppError> {
        let user = self.authenticate_user(credentials).await?;
        self.create_token(&user).await
    }

    pub async fn get_user_by_email(&self, email: &str) -> Result<User, AppError> {
        let user_model = Users::find()
            .filter(UsersColumn::Email.eq(email))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("User not found".to_string()))?;

        Ok(User {
            id: user_model.id,
            email: user_model.email,
            hashed_password: user_model.hashed_password,
            avatar_url: user_model.avatar_url,
            is_admin: user_model.is_admin,
            created_at: chrono::DateTime::from_naive_utc_and_offset(
                user_model.created_at,
                chrono::Utc,
            ),
            updated_at: chrono::DateTime::from_naive_utc_and_offset(
                user_model.updated_at,
                chrono::Utc,
            ),
        })
    }

    // Admin functionality methods
    pub async fn list_users(
        &self,
        page: i64,
        page_size: i64,
        search: Option<String>,
    ) -> Result<(Vec<User>, i64), AppError> {
        let page = if page < 1 { 1 } else { page };
        let page_size = if page_size < 1 { 20 } else { page_size };
        let offset = (page - 1) * page_size;

        // Build base query
        let mut query = Users::find();

        // Add search filter if provided
        if let Some(search_term) = search {
            if !search_term.trim().is_empty() {
                query = query.filter(
                    sea_orm::Condition::any().add(UsersColumn::Email.contains(&search_term)),
                );
            }
        }

        // Get total count
        let total = query.clone().count(&self.db).await?;

        // Get paginated results
        let user_models = query
            .order_by_asc(UsersColumn::Email)
            .offset(offset as u64)
            .limit(page_size as u64)
            .all(&self.db)
            .await?;

        let users: Vec<User> = user_models
            .into_iter()
            .map(|model| User {
                id: model.id,
                email: model.email,
                hashed_password: model.hashed_password,
                avatar_url: model.avatar_url,
                is_admin: model.is_admin,
                created_at: chrono::DateTime::from_naive_utc_and_offset(
                    model.created_at,
                    chrono::Utc,
                ),
                updated_at: chrono::DateTime::from_naive_utc_and_offset(
                    model.updated_at,
                    chrono::Utc,
                ),
            })
            .collect();

        Ok((users, total as i64))
    }

    pub async fn get_user_by_id(&self, user_id: i64) -> Result<User, AppError> {
        let user_model = Users::find()
            .filter(UsersColumn::Id.eq(user_id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("User not found".to_string()))?;

        Ok(User {
            id: user_model.id,
            email: user_model.email,
            hashed_password: user_model.hashed_password,
            avatar_url: user_model.avatar_url,
            is_admin: user_model.is_admin,
            created_at: chrono::DateTime::from_naive_utc_and_offset(
                user_model.created_at,
                chrono::Utc,
            ),
            updated_at: chrono::DateTime::from_naive_utc_and_offset(
                user_model.updated_at,
                chrono::Utc,
            ),
        })
    }

    pub async fn toggle_admin_status(&self, user_id: i64) -> Result<User, AppError> {
        let user_model = Users::find()
            .filter(UsersColumn::Id.eq(user_id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("User not found".to_string()))?;

        // Toggle admin status
        let new_admin_status = !user_model.is_admin;

        let mut active_model = user_model.into_active_model();
        active_model.is_admin = Set(new_admin_status);
        active_model.updated_at = Set(chrono::Utc::now().naive_utc());

        let updated_model = active_model.update(&self.db).await?;

        Ok(User {
            id: updated_model.id,
            email: updated_model.email,
            hashed_password: updated_model.hashed_password,
            avatar_url: updated_model.avatar_url,
            is_admin: updated_model.is_admin,
            created_at: chrono::DateTime::from_naive_utc_and_offset(
                updated_model.created_at,
                chrono::Utc,
            ),
            updated_at: chrono::DateTime::from_naive_utc_and_offset(
                updated_model.updated_at,
                chrono::Utc,
            ),
        })
    }

    pub async fn delete_user(&self, user_id: i64) -> Result<bool, AppError> {
        let user_model = Users::find()
            .filter(UsersColumn::Id.eq(user_id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("User not found".to_string()))?;

        let user_email = user_model.email.clone();
        let user_id_for_log = user_model.id;

        // Delete the user
        user_model.delete(&self.db).await?;

        info!("Deleted user: {} (ID: {})", user_email, user_id_for_log);
        Ok(true)
    }

    pub async fn reset_password(&self, user_id: i64, new_password: &str) -> Result<User, AppError> {
        let user_model = Users::find()
            .filter(UsersColumn::Id.eq(user_id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("User not found".to_string()))?;

        // Hash the new password
        let hashed_password = hash_password(new_password)?;

        // Update the password
        let mut active_model = user_model.into_active_model();
        active_model.hashed_password = Set(hashed_password);
        active_model.updated_at = Set(chrono::Utc::now().naive_utc());

        let updated_model = active_model.update(&self.db).await?;

        info!(
            "Reset password for user: {} (ID: {})",
            updated_model.email, updated_model.id
        );

        Ok(User {
            id: updated_model.id,
            email: updated_model.email,
            hashed_password: updated_model.hashed_password,
            avatar_url: updated_model.avatar_url,
            is_admin: updated_model.is_admin,
            created_at: chrono::DateTime::from_naive_utc_and_offset(
                updated_model.created_at,
                chrono::Utc,
            ),
            updated_at: chrono::DateTime::from_naive_utc_and_offset(
                updated_model.updated_at,
                chrono::Utc,
            ),
        })
    }

    /// Authenticate user using Basic Auth credentials (for DVC compatibility)
    pub async fn authenticate_basic_auth(&self, auth_header: &str) -> Result<User, AppError> {
        debug!("Attempting Basic Auth authentication");

        // Extract and decode Basic Auth credentials
        let encoded_credentials = auth_header
            .strip_prefix("Basic ")
            .ok_or_else(|| AppError::Auth("Invalid Basic Auth format".to_string()))?;

        let decoded_credentials = general_purpose::STANDARD
            .decode(encoded_credentials)
            .map_err(|_| AppError::Auth("Invalid base64 encoding".to_string()))?;

        let credentials_str = String::from_utf8(decoded_credentials)
            .map_err(|_| AppError::Auth("Invalid UTF-8 encoding".to_string()))?;

        let (email, password) = credentials_str
            .split_once(':')
            .ok_or_else(|| AppError::Auth("Invalid credentials format".to_string()))?;

        // Use existing authenticate_user method
        let credentials = UserLogin {
            email: email.to_string(),
            password: password.to_string(),
        };

        self.authenticate_user(&credentials).await
    }
}
